package com.hpf.redis7.mylock;

import cn.hutool.core.util.IdUtil;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 我们自研的redis分布式锁，实现了Lock接口
 */
//@Component 引入DistributedLockFactory工厂模式，从工厂获得即可
@AllArgsConstructor
public class RedisDistributedLock implements Lock {
    private StringRedisTemplate stringRedisTemplate;
    private String lockName;//KEYS[1]
    private String uuidValue;//ARGV[1]

    @Override
    public void lock() {
        tryLock();
    }

    @Override
    public boolean tryLock() {
        try {
            return tryLock(30L, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        String script = """
                if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then
                    redis.call('hincrby',KEYS[1],ARGV[1],1)
                    redis.call('expire',KEYS[1],ARGV[2])
                    return 1
                else
                    return 0
                end
                """;
        System.out.println("tryLock(): lockName:" + lockName + "\t" + "uuidValue:" + uuidValue);

        while (Boolean.FALSE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Collections.singletonList(lockName), uuidValue, String.valueOf(time)))) {
            //暂停60毫秒
            try {
                TimeUnit.MILLISECONDS.sleep(60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //新建一个后台扫描程序，来检查key目前的ttl，是否到我们规定的1/2 1/3来实现续期
        renewExpire(time);
        return true;
    }


    @Override
    public void unlock() {
        System.out.println("unlock(): lockName:" + lockName + "\t" + "uuidValue:" + uuidValue);
        String script = """
                if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then
                    return nil
                elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then
                    return redis.call('del',KEYS[1])
                else
                    return 0
                end
                """;
        // nil = false 1 = true 0 = false
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockName), uuidValue);

        if (null == flag) {
            throw new RuntimeException("this lock doesn't exists，o(╥﹏╥)o");
        }
    }

    /**
     * 自动续期。每过1/3时长，再次续期
     *
     * @param time 续期时长（单位：秒）
     */
    private void renewExpire(long time) {
        String script = """
                if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then
                    return redis.call('expire',KEYS[1],ARGV[2])
                else
                    return 0
                end
                """;

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (Boolean.TRUE.equals(stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Collections.singletonList(lockName), uuidValue, String.valueOf(time)))) {
                    renewExpire(time);
                }
            }
        }, (time * 1000) / 3);
    }


    //====下面两个暂时用不到，不再重写
    //====下面两个暂时用不到，不再重写
    //====下面两个暂时用不到，不再重写
    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public Condition newCondition() {
        return null;
    }
}
